require "pathname"
additional_path = Pathname.new(__FILE__).realpath.dirname.join("..", "..", "..", "..")
$: << additional_path
load_paths << additional_path

require "fileutils"
require "set"

require "wondercap/lib/deployment/core/deployment_transaction_mixin"
require "wondercap/lib/deployment/core/old_snapshots_cleaner"
require "wondercap/lib/deployment/core/snapshot_transaction"
require "wondercap/lib/utils/logging"

# requires for basic objects used by strategies
require "wondercap/lib/deployment/core/deployment_list"
##

load "wondercap/lib/deployment/recipes/db"
load "wondercap/lib/multi_configuration/recipes/multi_configuration"

class << self
  include ::Wondercap::Deployment::DeploymentTransactionMixin
end

namespace :wondercap do
  set :force_sources, false unless exists?(:force_sources)
  set :force_build, false unless exists?(:force_build)
  set :force_deploy, false unless exists?(:force_deploy)
    
  after "wondercap:init_multi_configuration" do
    mc_search_paths.insert(0, *[File.join(additional_path, "wondercap", "lib", "deployment", "core", "strategies"),
      File.join(additional_path, "wondercap", "lib", "deployment", "core", "filters"),
      File.join(additional_path, "wondercap", "lib", "deployment", "core", "extensions"),
      File.join(additional_path, "wondercap", "lib", "deployment", "extensions")])

    outer_conf = self.top
    configuration(:base) do
      set :project_name, configuration_name.to_s
      set :force_sources, outer_conf.force_sources
      set :force_build, outer_conf.force_build
      set :force_deploy, outer_conf.force_deploy

      use :base => :base
      use :build => :prepare

      use :runtime_dependencies => :explicit
      use :deployment_list => :explicit

      if outer_conf.exists?(:revision) && outer_conf.supplied_names.include?(name.to_s)
        use :revision => :explicit do 
          set :revision, outer_conf.revision.to_i
        end
      else
        use :revision => :head
        use :revision => :generic
      end

      use :src_patch => :copy
      use :dist_patch => :copy

      unless outer_conf.remote
        use :deployment => :local_build
      else
        use :deployment => :copy
      end
    end
  end
  
  before "wondercap:process_multi_configuration_files" do
    after "wondercap:process_multi_configuration_files" do
      outer_conf = self.top
      configuration :post_base do
          
        unless outer_conf.remote
          if outer_conf.force_build
            use :build => :one_time_wrapper
          else
            use :build => :persistent_one_time_wrapper
          end

          unless outer_conf.top_task.fully_qualified_name == "wondercap:deploy"
            use :revision => :last_sources_with_fallback
            use :revision => :current_local_snapshot_with_fallback
          end
        else
          use :revision => :current_local_snapshot
          use :runtime_dependencies => :current_local_snapshot
        end        

        use :dist_patch => :link_scripts
        if outer_conf.force_sources
          use :sources => :one_time_wrapper
        else
          use :sources => :persistent_one_time_wrapper
        end
      end
          
    end
  end
  
  before "wondercap:apply_area" do
    sid = fetch :sid, nil
    if sid && area_name.nil?
      set :area_name, Wondercap::Deployment::Snapshot::find_by_id(Integer(sid)).area.name
      set :remote, true unless area_name == "local"
    end
  end
  task "init_area_and_snapshot" do
    raise "no area '#{area_name}' defined" unless configuration_defined?(area_name)
    set :area, Wondercap::Deployment::Area.find_or_create_by_name(area_name)
    
    sid = fetch :sid, nil
    if !sid
      set :snapshot, area.current_snapshot
    else
      set :snapshot, Wondercap::Deployment::Snapshot::find_by_id(Integer(sid))
      raise "snapshot with specified id not found: #{sid}" unless snapshot
    end    
  end
  after "wondercap:apply_area", "wondercap:init_area_and_snapshot"

  # NOTE: ensures that "after"-callback is added as late as possible
  before "wondercap:extract_filters" do
    after "wondercap:extract_filters" do
      if top_task.fully_qualified_name == "wondercap:deploy"
        filters << :append_runtime_required if remote        
        filters << :order_by_runtime_dependencies
        if remote
          filters << :remote_fresh_only
        else
          filters << :local_fresh_only
        end        
      end
    end
  end
  
  task :snapshot_diff do
    raise "there is no current snapshot for area" unless area.current_snapshot
    dest_snapshot = area.current_snapshot
    
    raise "please specify the source snapshot id via 'src_sid' variable" unless exists? :src_sid
    src_snapshot = area.find_snapshot_by_id(src_sid.to_i)
    raise "no source snapshot with id '#{src_sid}' found" unless src_snapshot
    
    ui.say "area: #{area.name}"
    ui.say "source snapshot: id=#{src_snapshot.id}, created at #{src_snapshot.created_at}"
    ui.say "dest. snapshot : id=#{dest_snapshot.id}, created at #{dest_snapshot.created_at}"
    
    ui.say ui.table {
      header_col "Name", 32
      header_col "Source revision", 14
      header_col "Dest. revision", 14
      
      dest_snapshot.diff(src_snapshot).each do |diff_dep|
        src_dep = src_snapshot.find_deployment_by_name(diff_dep.name)
        
        row << diff_dep.name << (src_dep ? src_dep.revision : "") << diff_dep.revision
      end
    }
  end
   
  task :inspect_snapshot do
    unless snapshot.nil?
      ui.say "area: #{snapshot.area.name}"
      ui.say "snapshot: id=#{snapshot.id}, created at #{snapshot.created_at}"
      prev_snapshot = area.previous_snapshot_for_snapshot(snapshot)
      
      show_deps_name = fetch(:show_deps, nil)
      
      prev_snapshot_diff = prev_snapshot.diff(snapshot)
      deps = snapshot.deployments
      ui.say ui.table {
        header_col "Id", 4
        header_col "Name", 32
        header_col "Revision", 14
        header_col "New (prev. revision)", 20
        header_col "Required by #{show_deps_name}", 4 if show_deps_name
        
        deps.each do |dep|
          prev_dep = prev_snapshot_diff.find { |prev_dep| prev_dep.name == dep.name }
          row << dep.id << dep.name << dep.revision << (prev_dep ? "X (#{prev_dep.revision})" : "")
          row << (dep.runtime_dependencies.include?(show_deps_name) ? "X" : "") if show_deps_name
        end
      }
    else
      ui.say "there is no current snapshot for area"
    end
  end
  
  task :inspect_area do
    ui.say "#{area.name}:"
    
    cur_snap = area.current_snapshot
    unf_snap = area.unfinished_snapshot
    snaps = area.existent_snapshots
    ui.say ui.table {
      header_col "Id", 4
      header_col "Created at", 32
      header_col "Current", 10
      header_col "Unfinished", 10
      
      snaps.each do |snap|
        row << snap.id << snap.created_at << (snap == cur_snap ? "X" : "") << (snap == unf_snap ? "X" : "")
      end
    }
  end
  
  task :clean_local_builds do
    next if remote
    
    per_project_builds_limit = Integer(fetch(:per_project_builds_limit, 4))
    names.each do |name|
      conf = configuration_clone(name)
      conf.build_repository.sync
      conf.build_repository.remove_outdated(per_project_builds_limit)
    end
  end
  after "wondercap:deploy", "wondercap:clean_local_builds"
  
  task :clean_area do
    existent_snapshots_limit = Integer(fetch(:existent_snapshots_limit, 10))
    next if existent_snapshots_limit <= 0

    Wondercap::Deployment::OldSnapshotsCleaner.clean_old_snapshots(area, existent_snapshots_limit, deployment_transaction_getter)
  end
  after "wondercap:deploy", "wondercap:clean_area"

  on :start do
    unless fetch(:no_pid_file, false)
      Wondercap::Deployment::SnapshotTransactionCleaner.new(deployment_transaction_getter).clean_unfinished_snapshot(area)    
    end
  end
  
  before "wondercap:deploy", "wondercap:filter_names"
  task :deploy do
    if names.empty?
      logger.info "no names to process"
      next
    end
    
    deployments = filtered_configurations.map do |conf|
      Wondercap::Deployment::Deployment.new(:name => conf.project_name, :revision => conf.real_revision)
    end
    filtered_confs_hash = {}
    filtered_configurations.each { |conf| filtered_confs_hash[conf.project_name] = conf }
    fallback_getter = deployment_transaction_getter
    filtered_deployments_getter = lambda do |dep|
      conf = filtered_confs_hash[dep.name]
      if conf.nil? || conf.real_revision != dep.revision
        fallback_getter.call dep
      else
        conf
      end
    end
    
    Wondercap::Deployment::SnapshotTransactionCleaner.new(deployment_transaction_getter).clean_unfinished_snapshot(area)
    old_current_snapshot = area.current_snapshot
    begin
      Wondercap::Deployment::SnapshotTransaction.new(filtered_deployments_getter).deploy_new_snapshot(area, deployments, { :ignore_errors => fetch(:ignore_errors, false)})
      logger.important "for fast rollback use command: wcap fast_rollback sid=#{old_current_snapshot.id}" if old_current_snapshot && area.current_snapshot != old_current_snapshot
      trigger :deployment_succeeded, current_task
    rescue => e
      trigger :deployment_failed, current_task
      raise e
    end
  end
  
  task :rollback_to_snapshot do
    raise "can't rollback as there is no current snapshot in the area" if snapshot.nil?
    raise "can't rollback to current snapshot" if snapshot == area.current_snapshot
    raise "can't rollback to unfinished snapshot" if snapshot == area.unfinished_snapshot
    raise "can't rollback to non-existent snapshot" unless snapshot.exists?
    
    Wondercap::Deployment::SnapshotTransactionCleaner.new(deployment_transaction_getter).clean_unfinished_snapshot(area)
      Wondercap::Deployment::Area.transaction do
        area.unfinished_snapshot = area.build_snapshot_from_snapshot(area.current_snapshot)
        area.current_snapshot = snapshot

        begin
          Wondercap::Deployment::SnapshotTransactionCleaner.new(deployment_transaction_getter).clean_unfinished_snapshot(area)
          trigger :rollback_succeeded, current_task
        rescue => e
          trigger :rollback_failed, current_task
          raise e
        end
      end
  end
  
  task :fast_rollback do
    raise "can't do fast rollback as there is current snapshot in the area" if snapshot.nil?    
    raise "destination snapshot is not previous to current - possibly other deployments had been performed" if area.previous_snapshot_for_snapshot(area.current_snapshot) != snapshot
    
    rollback_to_snapshot
  end

end
